{\rtf1\ansi\ansicpg1252\cocoartf2580 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fmodern\fcharset0 Courier;\f1\froman\fcharset0 Times-Roman;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0;} {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc0\levelnfcn0\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{decimal\}.}{\leveltext\leveltemplateid1\'02\'00.;}{\levelnumbers\'01;}\fi-360\li720\lin720 }{\listname ;}\listid1}} {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}} \margl1440\margr1440\vieww11520\viewh8400\viewkind0 \deftab720 \pard\pardeftab720\partightenfactor0 \f0\fs24 \cf2 \expnd0\expndtw0\kerning0 \outl0\strokewidth0 \strokec2 ';\ \}\ add_shortcode( 'tracyradio_carplay', 'tracyradio_carplay_shortcode_handler' );\ \ // 2. Enqueue script and styles, and pass data to the script\ function tracyradio_enqueue_scripts() \{\ // We only want to load these on pages where the shortcode is present.\ // This is a simple check; more complex sites might need a more robust solution.\ if ( is_singular() && has_shortcode( get_post()->post_content, 'tracyradio_carplay' ) ) \{\ \ // Register the script but don't enqueue it yet.\ wp_register_script(\ 'tracyradio-player',\ false, // We will add the script inline.\ [],\ '25.0629.1',\ true // Load in the footer.\ );\ \ // Add the main JavaScript logic inline.\ wp_add_inline_script( 'tracyradio-player', tracyradio_get_player_javascript() );\ \ // Localize script to pass PHP variables to JavaScript.\ // This is the correct way to pass the AJAX URL and nonce.\ wp_localize_script( 'tracyradio-player', 'tracyRadioData', [\ 'ajax_url' => admin_url( 'admin-ajax.php' ),\ 'nonce' => wp_create_nonce( 'tracyradio-metadata-nonce' ),\ ]);\ \ // Now enqueue the script.\ wp_enqueue_script( 'tracyradio-player' );\ \ // Add styles inline.\ wp_add_inline_style( 'tracyradio-player', tracyradio_get_player_css() );\ \}\ \}\ add_action( 'wp_enqueue_scripts', 'tracyradio_enqueue_scripts' );\ \ \ // 3. The server-side proxy for fetching metadata securely\ function tracyradio_get_metadata_ajax_handler() \{\ // Verify the request to prevent misuse.\ check_ajax_referer( 'tracyradio-metadata-nonce', '_nonce' );\ \ // Get the URL from the AJAX request.\ $url = isset( $_POST['url'] ) ? esc_url_raw( $_POST['url'] ) : '';\ \ if ( empty( $url ) ) \{\ wp_send_json_error( 'URL is missing.' );\ return;\ \}\ \ // Use WordPress's built-in HTTP functions to make the request.\ $response = wp_remote_get( $url, [\ 'timeout' => 10,\ 'headers' => [ 'Accept' => 'application/json' ],\ ]);\ \ if ( is_wp_error( $response ) ) \{\ wp_send_json_error( $response->get_error_message() );\ return;\ \}\ \ $body = wp_remote_retrieve_body( $response );\ $data = json_decode( $body, true );\ \ // Check if JSON decoding was successful.\ if ( json_last_error() !== JSON_ERROR_NONE ) \{\ // Sometimes the body might not be perfect JSON, so we send the raw text as a fallback.\ wp_send_json_success( ['songtitle' => $body] );\ return;\ \}\ \ wp_send_json_success( $data );\ \}\ // Hook for both logged-in and non-logged-in users.\ add_action( 'wp_ajax_tracyradio_get_metadata', 'tracyradio_get_metadata_ajax_handler' );\ add_action( 'wp_ajax_nopriv_tracyradio_get_metadata', 'tracyradio_get_metadata_ajax_handler' );\ \ \ // 4. Function to return the CSS for the player.\ function tracyradio_get_player_css() \{\ return "\ /* Basic reset for full-screen player */\ html, body \{ height: 100%; margin: 0; padding: 0; overflow: hidden; background-color: #000; \}\ #tracyradio-player-container \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 99999; \}\ body \{ font-family: 'Inter', sans-serif; -webkit-tap-highlight-color: transparent; \}\ #app-container \{ background-color: transparent; z-index: 10; \}\ #background-artwork \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; background-size: cover; background-position: center; filter: blur(25px) brightness(0.6); transition: opacity 0.6s ease-in-out; opacity: 1; transform: scale(1.1); \}\ #visualizer-canvas \{ position: fixed; left: 0; bottom: 0; width: 100%; height: 100%; z-index: 0; opacity: 0.5; transition: opacity 0.5s ease-in-out; \}\ .control-btn:focus-visible \{ outline: 3px solid #85ad85; outline-offset: 2px; border-radius: 9999px; \}\ h1, h2, p \{ text-shadow: 2px 2px 5px rgba(0,0,0,0.7); \}\ #station-logo \{ filter: drop-shadow(0px 4px 6px rgba(0,0,0,0.6)); max-height: 100%; max-width: 80%; width: auto; object-fit: contain; transition: opacity 0.5s ease-in-out; \}\ #station-display \{ height: 100px; display: flex; align-items: center; justify-content: center; \}\ #other-stations-preview \{ backdrop-filter: blur(10px); background-color: rgba(255,255,255,0.05); -webkit-backdrop-filter: blur(10px); overflow: hidden; \}\ #station-preview-container \{ overflow-x: auto; -webkit-overflow-scrolling: touch; \}\ #station-preview-container::-webkit-scrollbar \{ display: none; \}\ #station-preview-container \{ -ms-overflow-style: none; scrollbar-width: none; \}\ .station-preview-item \{ cursor: pointer; transition: transform 0.2s ease-in-out; flex-shrink: 0; \}\ .station-preview-item:hover \{ transform: scale(1.1); \}\ .station-preview-img \{ width: 56px; height: 56px; border: 2px solid rgba(255,255,255,0.4); transition: border-color 0.3s, box-shadow 0.3s; \}\ .station-preview-img.glowing \{ border-color: #669966; box-shadow: 0 0 15px #669966; \}\ #playing-channel-name \{ transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out; transform: translateY(-10px); opacity: 0; height: 0; \}\ #playing-channel-name.visible \{ transform: translateY(0); opacity: 1; height: auto; margin-bottom: 0.25rem; \}\ .modal-overlay \{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; display: none; align-items: center; justify-content: center; background-color: rgba(0,0,0,0.6); backdrop-filter: blur(5px); opacity: 0; transition: opacity 0.3s ease-in-out; \}\ .modal-overlay.visible \{ display: flex; opacity: 1; \}\ .modal-content \{ background-color: #1f2937; border-radius: 1rem; width: 90%; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05); transform: scale(0.95); transition: transform 0.3s ease-in-out; display: flex; flex-direction: column; overflow: hidden; \}\ .modal-overlay.visible .modal-content \{ transform: scale(1); \}\ #whats-playing-modal .modal-content \{ max-width: 400px; max-height: 80vh; \}\ #request-modal .modal-content \{ max-width: 600px; height: 80vh; \}\ .modal-header \{ padding: 1rem 1.5rem; border-bottom: 1px solid #374151; flex-shrink: 0; \}\ #whats-playing-list, #request-iframe-wrapper \{ overflow-y: auto; padding: 1rem 1.5rem; \}\ #request-iframe-wrapper \{ padding: 0; flex-grow: 1; \}\ #request-iframe \{ width: 100%; height: 100%; border: none; \}\ .modal-list-item \{ padding-bottom: 0.75rem; margin-bottom: 0.75rem; border-bottom: 1px solid #374151; \}\ .modal-list-item:last-child \{ border-bottom: none; margin-bottom: 0; padding-bottom: 0;\}\ .modal-list-item[data-station-index]:hover \{ background-color: #374151; cursor: pointer; \}\ @keyframes dynamic-pause-background \{ 0% \{ background-position: 0% 50%; \} 50% \{ background-position: 100% 50%; \} 100% \{ background-position: 0% 50%; \} \}\ .paused-background \{ background: linear-gradient(270deg, #000000, #2c003e, #000000); background-size: 200% 200%; animation: dynamic-pause-background 20s ease infinite; \}\ ";\ \}\ \ // 5. Function to return the JavaScript for the player.\ function tracyradio_get_player_javascript() \{\ return "\ const rootEl = document.getElementById('tracyradio-player-container');\ if (!rootEl) return;\ \ rootEl.innerHTML = `\ \
\ \
\
\

\

TracyRadio.com

\
\
\
\
\ \\"Station\

Select a Station

\
\
\

...

\

\
\
\
\ \ \ \
\
\ \
\
\ \
\
\
\
\

What's Playing Now

\ \
\ \
\
\
\
\
\

Song Request

\ \
\
\ \
\
\
\ \ `;\ \ const stations = [\ \{ id: 'hippie-soul', name: 'Hippie Soul Cafe', streamUrl: 'https://ec2.yesstreaming.net:2350/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2350', artwork: 'eab308', logoUrl: 'https://tracyradio.com/wp-content/uploads/2024/07/1.png', requestUrl: 'https://tracyradio.com/hippie-soul-cafe-request/' \},\ \{ id: 'rnb-vibe', name: 'R&B Vibe', streamUrl: 'https://ec2.yesstreaming.net:1450/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:1450', artwork: '8b5cf6', logoUrl: 'https://tracyradio.com/wp-content/uploads/2024/09/RBVibe-blak.png', requestUrl: 'https://tracyradio.com/rb-vibe-request/' \},\ \{ id: '90s-groove', name: \\"90's Groove\\", streamUrl: 'https://ec2.yesstreaming.net:2500/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2500', artwork: 'ec4899', logoUrl: 'https://ec2.yesstreaming.net:4250/media/widgets/90sGroove-SQ.png', requestUrl: 'https://tracyradio.com/90s-groove-request' \},\ \{ id: 'lost-80s', name: 'Lost in the 80s', streamUrl: 'https://ec2.yesstreaming.net:2840/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2840', artwork: 'ef4444', logoUrl: 'https://ec2.yesstreaming.net:4250/media/widgets/channels4_profile.jpg', requestUrl: 'https://tracyradio.com/lost-in-the-80s-request' \},\ \{ id: 'crazy-love', name: 'Crazy Love', streamUrl: 'https://ec2.yesstreaming.net:2330/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2330', artwork: 'f472b6', logoUrl: 'https://ec2.yesstreaming.net:4250/media/widgets/Crazy_Love_logos_HzmH1J6.png', requestUrl: 'https://tracyradio.com/crazy-love-request' \},\ \{ id: 'instrumental', name: 'InstruMENTAL', streamUrl: 'https://ec2.yesstreaming.net:2730/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2730', artwork: '22d3ee', logoUrl: 'https://tracyradio.com/wp-content/uploads/2025/03/InstrMENTAL-LOGO-TracyRadio-600-jpg-Logos.jpg', requestUrl: 'https://tracyradio.com/instrumental-request' \},\ \{ id: 'party-1999', name: 'Party1999', streamUrl: 'https://ec2.yesstreaming.net:4150/;?type=http&nocache=1751215746', metadataUrl: 'http://ec2.yesstreaming.net:4150', artwork: 'd946ef', logoUrl: 'https://ec2.yesstreaming.net:4250/media/widgets/10_ZXKb4GN.png', requestUrl: 'https://tracyradio.com/party1999-request' \},\ \{ id: 'lofi-affirmations', name: 'Lofi Affirmations', streamUrl: 'https://ec2.yesstreaming.net:2730/;?type=http&nocache=1', metadataUrl: 'http://ec2.yesstreaming.net:2730', artwork: '34d399', logoUrl: 'https://ec2.yesstreaming.net:4250/media/widgets/LofiAffirmations-LOGO-transparent_k6JN6zC.png', requestUrl: 'https://tracyradio.com/lofi-affirmations-request' \}\ ];\ \ let currentStationIndex = 0;\ let isPlaying = false;\ let metadataInterval = null;\ let currentSongArtworkUrl = null; \ let audioCtx, analyser, source, visualizerFrameId;\ \ const backgroundArtworkEl = document.getElementById('background-artwork');\ const visualizerCanvas = document.getElementById('visualizer-canvas');\ const audioPlayer = document.getElementById('audio-player');\ const playerControlBtn = document.getElementById('player-control');\ const prevStationBtn = document.getElementById('prev-station-btn');\ const nextStationBtn = document.getElementById('next-station-btn');\ const stationLogoEl = document.getElementById('station-logo');\ const currentStationNameEl = document.getElementById('current-station-name');\ const artistNameEl = document.getElementById('artist-name');\ const songTitleEl = document.getElementById('song-title');\ const stationPreviewContainer = document.getElementById('station-preview-container');\ const playingChannelNameEl = document.getElementById('playing-channel-name');\ const whatsPlayingModal = document.getElementById('whats-playing-modal');\ const whatsPlayingBtn = document.getElementById('whats-playing-btn');\ const closeWhatsPlayingModalBtn = document.getElementById('close-whats-playing-modal-btn');\ const whatsPlayingList = document.getElementById('whats-playing-list');\ const requestModal = document.getElementById('request-modal');\ const requestBtn = document.getElementById('request-btn');\ const requestIframe = document.getElementById('request-iframe');\ const requestModalTitle = document.getElementById('request-modal-title');\ const closeRequestModalBtn = document.getElementById('close-request-modal-btn');\ \ const playIcon = ``;\ const pauseIcon = ``;\ \ async function fetchArtwork(artist, track) \{ /* ... same as before ... */ \}\ async function getStationMetadata(station) \{ /* ... updated function ... */ \}\ async function updateCurrentStationMetadata() \{ /* ... same as before ... */ \}\ async function showWhatsPlaying() \{ /* ... same as before ... */ \}\ function selectStation(index) \{ /* ... same as before ... */ \}\ function playAudio() \{ /* ... same as before ... */ \}\ function pauseAudio() \{ /* ... same as before ... */ \}\ function togglePlayPause() \{ /* ... same as before ... */ \}\ function setBackground(imageUrl, usePausedAnimation = false) \{ /* ... same as before ... */ \}\ function updateBackgroundArtwork(artworkUrl, station) \{ /* ... same as before ... */ \}\ function updateMainDisplayArtwork() \{ /* ... same as before ... */ \}\ function updateUI() \{ /* ... same as before ... */ \}\ function renderStationPreviews() \{ /* ... same as before ... */ \}\ function setupVisualizer() \{ /* ... same as before ... */ \}\ function renderVisualizer() \{ /* ... same as before ... */ \}\ function init() \{ /* ... same as before, with event listeners */ \}\ init();\ \});\ ";\ \}\ \ \pard\tx220\tx720\pardeftab720\li720\fi-720\sa240\partightenfactor0 \ls1\ilvl0 \f1 \cf2 \ }